1 using System;
2 using UnityEngine.Assertions;
3 using Random = UnityEngine.Random;
4
5 namespace ProceduralToolkit.Examples
6 {
7 /// <summary>
8 /// Generic cellular automaton for two-state rulesets
9 /// </summary>
10 public class CellularAutomaton
11 {
12 [Serializable]
13 public class Config
14 {
15 public int width = 128;
16 public int height = 128;
17 public Ruleset ruleset = Ruleset.life;
18 public float startNoise = 0.25f;
19 public bool aliveBorders = false;
20 }
21
22 public CellState[,] cells;
23
24 private readonly Config config;
25 private readonly Action<int, int> visitAliveBorders;
26 private readonly Action<int, int> visitDeadBorders;
27 private CellState[,] copy;
28 private int aliveNeighbours;
29
30 public CellularAutomaton(Config config)
31 {
32 Assert.IsTrue(config.width > 0);
33 Assert.IsTrue(config.height > 0);
34
35 this.config = config;
36 cells = new CellState[config.width, config.height];
37 copy = new CellState[config.width, config.height];
38
39 visitAliveBorders = (int neighbourX, int neighbourY) =>
40 {
41 if (copy.IsInBounds(neighbourX, neighbourY))
42 {
43 if (copy[neighbourX, neighbourY] == CellState.Alive)
44 {
45 aliveNeighbours++;
46 }
47 }
48 else
49 {
50 aliveNeighbours++;
51 }
52 };
53 visitDeadBorders = (int neighbourX, int neighbourY) =>
54 {
55 if (copy[neighbourX, neighbourY] == CellState.Alive)
56 {
57 aliveNeighbours++;
58 }
59 };
60
61 FillWithNoise(config.startNoise);
62 }
63
64 public void Simulate(int generations)
65 {
66 for (int i = 0; i < generations; i++)
67 {
68 Simulate();
69 }
70 }
71
72 public void Simulate()
73 {
74 PTUtils.Swap(ref cells, ref copy);
75 for (int x = 0; x < config.width; x++)
76 {
77 for (int y = 0; y < config.height; y++)
78 {
79 int aliveCells = CountAliveNeighbourCells(x, y);
80
81 if (copy[x, y] == CellState.Dead)
82 {
83 if (config.ruleset.CanSpawn(aliveCells))
84 {
85 cells[x, y] = CellState.Alive;
86 }
87 else
88 {
89 cells[x, y] = CellState.Dead;
90 }
91 }
92 else
93 {
94 if (!config.ruleset.CanSurvive(aliveCells))
95 {
96 cells[x, y] = CellState.Dead;
97 }
98 else
99 {
100 cells[x, y] = CellState.Alive;
101 }
102 }
103 }
104 }
105 }
106
107 private void FillWithNoise(float noise)
108 {
109 for (int x = 0; x < config.width; x++)
110 {
111 for (int y = 0; y < config.height; y++)
112 {
113 cells[x, y] = Random.value < noise ? CellState.Alive : CellState.Dead;
114 }
115 }
116 }
117
118 private int CountAliveNeighbourCells(int x, int y)
119 {
120 aliveNeighbours = 0;
121 if (config.aliveBorders)
122 {
123 copy.VisitMooreNeighbours(x, y, false, visitAliveBorders);
124 }
125 else
126 {
127 copy.VisitMooreNeighbours(x, y, true, visitDeadBorders);
128 }
129 return aliveNeighbours;
130 }
131 }
132 }